<?php


class MY_CILog
{

    private $_log_path; //日志文件存放路径
    private $_date_path; //此刻记录日志存放的文件夹
    private $_file_permissions; //日志文件权限
    private $_date_fmt; //存放格式
    private $_enabled; //是否开启日志记录
    private $_threshold; //日志记录级别
    private $_levels; //级别对应值
    private $_debugInfo; //是否开启详细调试信息
    private $_allLog_controller; //包含的控制器将会记录所有的日志级别记录


    public function __construct()
    {
        $this->_file_permissions = 0644;
        $this->_date_fmt = 'Y-m-d H:i:s';
        $this->_enabled = TRUE;
        $this->_threshold = 1;
        $this->_levels = array('ERROR' => 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4);
        $this->_debugInfo = TRUE;
        $this->_allLog_controller = array();
        $config = &get_config(); //获取config配置信息
        $this->_log_path = ($config['CILog_path'] !== '') ? $config['CILog_path'] : APPPATH . 'CIlogs/'; //日志文件夹根目录地址
        if (!$this->_mkdir()) //创建日志存放结构失败或者不可写，则不记录日志
            $this->_enabled = FALSE;
        if (is_numeric($config['CILog_threshold'])) { //获取配置文件的日志记录级别
            $this->_threshold = (int)$config['CILog_threshold'];
        }
        if (!empty($config['CILog_date_fmt'])) { //获取日志记录的时间格式
            $this->_date_fmt = $config['CILog_date_fmt'];
        }
        if (!empty($config['CILog_file_permissions']) && is_int($config['CILog_file_permissions'])) { //获取生成文件的文件去那些
            $this->_file_permissions = $config['CILog_file_permissions'];
        }
        $this->_debugInfo = $config['CILog_debugInfo']; //是否开启调试信息和浏览器信息
        !empty($config['CILog_allLog_controller']) && $this->_allLog_controller = $config['CILog_allLog_controller']; //是否有特殊控制器
        if (!empty($this->_allLog_controller)) { //将特殊控制器中的所有值转为小写
            foreach ($this->_allLog_controller as &$controller) {
                $controller = strtolower($controller);
            }
        }
    }


    /**
     * 记录日志
     * @param type $level 级别
     * @param type $msg 日志内容
     * @param type $onlyMain 是否只将日志记入主流程
     */
    public function write_log($level, $msg, $onlyMain = false)
    {


        $level = strtoupper($level);


        if ($this->_enabled === FALSE) { //文件夹创建失败或者无法读取，则失败
            return FALSE;
        }


        //根据配置文件判断是否记录此类型的日志
        if ((!isset($this->_levels[$level]) OR (
                ($this->_levels[$level] > $this->_threshold) && !in_array(strtolower(REQUEST_CLASS), $this->_allLog_controller)))
        ) {
            return FALSE;
        }

        $mainfilePath = $this->_date_path . 'Main.log'; //main Log的存放路径
        $filePath = $this->_date_path . 'Class-' . REQUEST_CLASS . '.log'; //当前控制器的存放路径
        $message = ''; //待写入的日志内容
        //根据配置文件的日期参数获取写入文件的日期格式内容
        if (strpos($this->_date_fmt, 'u') !== FALSE) {
            $microtime_full = microtime(TRUE);
            $microtime_short = sprintf("%06d", ($microtime_full - floor($microtime_full)) * 1000000);
            $date = new DateTime(date('Y-m-d H:i:s.' . $microtime_short, $microtime_full));
            $date = $date->format($this->_date_fmt);
        } else {
            $date = date($this->_date_fmt);
        }


        //判断是否第一次生成文件
        if (!is_file($mainfilePath)) {
            $notMainFile = true;
        }
        if (!is_file($filePath) && !$onlyMain) {
            $notClassFile = true;
        }


        $message = $this->_disposalData($level, $date, $msg); //拼接每条日志的记录
        file_put_contents($mainfilePath, $message, FILE_APPEND | LOCK_EX); //将内容追加写入主日志文件
        if (!$onlyMain) { //如果不是设置只写主日志文件，默认都写入对应的控制器日志文件
            file_put_contents($filePath, $message, FILE_APPEND | LOCK_EX); //将内容追加写入主日志文件
        }


        //修改新生成的日志文件的权限
        if (isset($notMainFile) && $notMainFile === TRUE) {
            chmod($mainfilePath, $this->_file_permissions);
        }
        if (isset($notClassFile) && $notClassFile === TRUE) {
            chmod($filePath, $this->_file_permissions);
        }
        return TRUE;
    }


    /**
     * 创建文件夹结构
     * @return boolean
     */
    private function _mkdir()
    {
        file_exists($this->_log_path) OR mkdir($this->_log_path, 0777, TRUE);
        if (!is_dir($this->_log_path) OR !is_really_writable($this->_log_path)) {
            return FALSE;
        }


        $datePath = $this->_log_path . date('Y', time()) . '/' . date('m', time()) . '/' . date('d', time()) . '/'; //创建以此刻日期为文件名的文件夹
        file_exists($datePath) OR mkdir($datePath, 0777, TRUE);
        if (!is_dir($datePath) OR !is_really_writable($datePath)) {
            return FALSE;
        }
        $this->_date_path = $datePath;
        return TRUE;
    }

    /**
     * 拼装日志记录信息
     * @param type $level 级别
     * @param type $date 时间
     * @param type $message 日志内容
     * @return type
     */
    private function _disposalData($level, $date, $message)
    {
        if ($level == 'INFO')
            $level = "$level"; //如果是INFO的日志，多加个空格，日志格式整齐一些
        $msg = $date . "   " . $level . "   " . IDNUM . "   " . REQUEST_CLASS . "/" . REQUEST_METHOD . "   " . $message . PHP_EOL;
        if (version_compare(phpversion(), '5.4.0') >= 0) { //debug_backtrace函数只有在5.4以上才支持第二个参数
            $debugInfo = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 4); //获取调试信息
        } else {
            $debugInfo = debug_backtrace();
        }
        //开启了调试信息配置且是ERROR级别且是由自定义的日志报错，才显示调试信息和浏览器信息
        if (($this->_debugInfo or in_array(strtolower(REQUEST_CLASS), $this->_allLog_controller)) && $level == "ERROR" && $debugInfo[3]['function'] !== 'write_log') {
            $msg .= '    调试信息：在' . $debugInfo[2]['file'] . "中的第{$debugInfo[2]['line']}行中调用记录日志方法，包含在{$debugInfo[3]['function']}方法中";
            if (isset($debugInfo[3]['file']))
                $msg .= "，调用{$debugInfo[3]['function']}的是{$debugInfo[3]['file']}中的{$debugInfo[3]['line']}行";
            if (!empty($debugInfo[3]['args'])) {
                $msg .= "，传入{$debugInfo[3]['function']}方法的参数值序列化后为：" . serialize($debugInfo[3]['args']) . PHP_EOL;
            } else {
                $msg .= "，无参数传入" . PHP_EOL;
            }
            $input = load_class('INPUT', 'core'); //加载INPUT辅助类
            //拼接IP地址和浏览器版本信息
            $msg .= "    浏览器信息：IP地址为{$input->ip_address()}，使用的浏览器是：" . $this->_getBrowser() . "(" . $this->_getBrowserVersion() . "版本)";
            //拼接访问方式
            $msg .= "，访问方式为：" . $input->method();
            if ($input->is_ajax_request()) //是否以ajax访问
                $msg .= "，以ajax形式访问";
            if ($input->is_cli_request()) //是否以cli访问
                $msg .= "，以cli形式访问";
            $HTTP_REFERER = $input->server('HTTP_REFERER');
            if (!empty($HTTP_REFERER)) //能够获取上个来源页
                $msg .= "，上个来源页是：{$input->server('HTTP_REFERER')}";
            $get = $input->get();
            if (!empty($get)) //如果传入的get参数不为空
                $msg .= "，传入的get参数序列化后为：" . serialize($input->get());
            $post = $input->post();
            if (!empty($post)) //如果传入的post参数不为空
                $msg .= "，传入的post参数序列化后为：" . serialize($input->post());
//            if (strlen($input->raw_input_stream) > 0) { //如果传入的input流不为空
//                $msg .= "，传入的input流序列化后为：". serialize($input->raw_input_stream);
//            }
//
            $msg .= PHP_EOL; //换行符
            if (isset($_SESSION))
                $msg .= "    SESSION信息序列化后为：" . serialize($_SESSION) . PHP_EOL;
        }
        $msg .= PHP_EOL; //换行符
        return $msg;
    }


    /**
     * 获取浏览器类型。
     * @return string
     */
    private function _getBrowser()
    {
        if (empty($_SERVER['HTTP_USER_AGENT']))
            return 'unknow';


        $agent = $_SERVER["HTTP_USER_AGENT"];
        if (strpos($agent, 'MSIE') !== false || strpos($agent, 'rv:11.0')) {
            return "ie";
        } else if (strpos($agent, 'Firefox') !== false) {
            return "firefox";
        } else if (strpos($agent, 'Chrome') !== false) {
            return "chrome";
        } else if (strpos($agent, 'Opera') !== false) {
            return 'opera';
        } else if ((strpos($agent, 'Chrome') == false) && strpos($agent, 'Safari') !== false) {
            return 'safari';
        } else {
            return 'unknown';
        }
    }


    /**
     * 获得浏览器版本号
     * @return string
     */
    private function _getBrowserVersion()
    {
        if (empty($_SERVER['HTTP_USER_AGENT']))
            return 'unknow';


        $agent = $_SERVER['HTTP_USER_AGENT'];
        if (preg_match('/MSIE\s(\d+)\..*/i', $agent, $regs)) {
            return $regs[1];
        } else if (preg_match('/FireFox\/(\d+)\..*/i', $agent, $regs)) {
            return $regs[1];
        } else if (preg_match('/Opera[\s|\/](\d+)\..*/i', $agent, $regs)) {
            return $regs[1];
        } else if (preg_match('/Chrome\/(\d+)\..*/i', $agent, $regs)) {
            return $regs[1];
        } else if ((strpos($agent, 'Chrome') == false) && preg_match('/Safari\/(\d+)\..*$/i', $agent, $regs)) {
            return $regs[1];
        } else if (preg_match('/rv:(\d+)\..*/i', $agent, $regs)) {
            return $regs[1];
        } else {
            return 'unknow';
        }
    }


}